(require (lib "galore-units.ss" "galore")
         (lib "list.ss")
         (lib "etc.ss")
         "test-harness.ss")

(use-fmap (instantiate-fmap unbalanced-search-tree-set@ numbers@) fmap)

;(print-tests #t)
(print-tests 'stop)

;; Like fmap-add-all, but for imperative tables.
(define (list->imp-hash lst)
  (let ([table (make-hash-table)])
    (for-each (lambda (pair) (hash-table-put! table (first pair) (second pair)))
              lst)
    table))

;; The inverse of above.
(define (imp-hash->list imp-hash)
  (hash-table-map imp-hash list))

;; filter-duplicates: listof a -> listof a
;;
;; Removes duplicate elements from lst, keeping the left-most elements.
(define (filter-duplicates lst)
  (let ([dealt-with (make-hash-table)])
    (let loop ([numbers lst])
      (cond [(empty? numbers) null]
            [(hash-table-get dealt-with (first numbers) (lambda () #f))
             (loop (rest numbers))]
            [else (hash-table-put! dealt-with (first numbers) #t)
                  (cons (first numbers) (loop (rest numbers)))]))))

(define (make-test-list length)
  (let ([keys (filter-duplicates (build-list length (lambda (i) (random (* length 1000)))))])
    (map (lambda (key) (list key (* 10 key))) keys)))


(define (key-value-list-equal? lst1 lst2)
  (let ([<=? (lambda (a b) (<= (first a) (first b)))])
    (equal? (quicksort lst1 <=?) (quicksort lst2 <=?))))

(define (test-remove fmap ihash elem)
  (let ([fmap (fmap:remove fmap elem)])
    (hash-table-remove! ihash elem)
    (if (key-value-list-equal? (fmap:pairs fmap) (imp-hash->list ihash))
        fmap
        (error 'test-remove "Could not remove ~s" elem))))

(define (test-get fmap ihash key)
  (let/ec k
    (let* ([f-value (fmap:get fmap key
                              (lambda ()
                                (hash-table-get ihash key (lambda () (k fmap)))
                                (error 'test-get "found ~s in the ihash but not in the fmap" key)))]
           [i-value (hash-table-get ihash key
                                    (lambda ()
                                      (error 'test-get "found ~s in the fmap but not in the ihash\n ~s \n ~s" key
                                             (fmap:pairs fmap) (imp-hash->list ihash))))])
      (if (equal? f-value i-value)
          fmap
          (error 'test-get "fmap contains ~s, but ihash contains ~s for key: ~s" f-value i-value key)))))

(define (test-add fmap ihash key value)
  (let ([fmap (fmap:insert fmap key value)])
    (hash-table-put! ihash key value)
    (if (key-value-list-equal? (fmap:pairs fmap) (imp-hash->list ihash))
        fmap
        (error 'test-add "Could not put key: ~s, value: ~s" key value))))

(define (random-test fmap ihash test-count)
  (let ([<=? (lambda (a b) (<= (first a) (first b)))])
    (test (mergesort (fmap:pairs fmap) <=?) (mergesort (imp-hash->list ihash) <=?)))
  (unless (zero? test-count)
    (case (random 3)
      ((0)
       (random-test (test-add fmap ihash (random 100) (random 100)) ihash (sub1 test-count)))
      ((1)
       (random-test (test-remove fmap ihash (random 100)) ihash (sub1 test-count)))
      ((2)
       (random-test (test-get fmap ihash (random 100)) ihash (sub1 test-count))))))

(define (test-us)
  (let* ([test-list (make-test-list 100)]
         [func-table (fmap:assoc->fmap test-list)]
         [imp-table (list->imp-hash test-list)])
    
    (test (fmap:size func-table) (hash-table-count imp-table))
    
    (test (fmap:pairs fmap:empty) null)
    (test (fmap:pairs (fmap:insert fmap:empty 'foo 'bar))
               '((foo bar)))
    (test (fmap:pairs (fmap:assoc->fmap '((3 foobar) (1 foo) (2 bar))))
               '((1 foo) (2 bar) (3 foobar)))
    (test (fmap:get (fmap:insert fmap:empty 1 'foo) 1) 'foo)
    (test (fmap:pairs (fmap:remove (fmap:assoc->fmap '((1 foo))) 1)) null)
    
    (test (fmap:size fmap:empty) 0))
  )
(test-us)
(random-test fmap:empty (make-hash-table) 1000)


